package net.taviscaron.drivenotes.api;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.IOException;
import java.io.Writer;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.taviscaron.drivenotes.api.action.ApiAction;
import net.taviscaron.drivenotes.shared.model.entity.User;
import net.taviscaron.drivenotes.shared.service.LoginService;
import net.taviscaron.drivenotes.web.auth.AuthorizationData;
import net.taviscaron.drivenotes.web.core.Constants;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
 * Api controller
 * @author Andrei Senchuk
 */
public class ApiController extends HttpServlet {
    private static final String AUTH_TOKEN_PARAM = "token";
    private static final String ACTION_PARAM = "act";
    private LoginService loginService;
    private RedisTemplate<String, AuthorizationData> authDataRedisTemplate;
    private Gson gson;
    private ApiActionFactory actionFactory;

    @Override
    @SuppressWarnings("unchecked")
    public void init () throws ServletException {
        super.init();

        WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
        loginService = (LoginService) context.getBean("loginService");
        authDataRedisTemplate = (RedisTemplate<String, AuthorizationData>) context.getBean("authDataRedisTemplate");
        actionFactory = (ApiActionFactory) context.getBean("apiActionFactory");
        gson = new GsonBuilder()
                .setExclusionStrategies(new UserEntityExclusionStrategy())
                .setPrettyPrinting()
                .create();
    }

    private Object dispatchRequest (HttpServletRequest request, HttpServletResponse response) {
        String action = request.getParameter(ACTION_PARAM);

        if ("login".equals(action)) {
            return login(request, response);
        } else if ("logout".equals(action)) {
            return logout(request, response);
        }

        ApiAction apiAction = actionFactory.getAction(action);
        if (apiAction == null) {
            return ResponseUtil.errorResponse(ApiError.NOT_FOUND, "not found");
        }

        if (!authorization(request, response)) {
            return ResponseUtil.errorResponse(ApiError.NOT_AUTHORIZED, "not authorized");
        }

        return apiAction.execute(request, response);
    }

    private Object login (HttpServletRequest request, HttpServletResponse response) {
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if(username == null || password == null) {
            return ResponseUtil.errorResponse(ApiError.BAD_REQUEST, "username or/and login is not specified");
        }

        User user = loginService.findUserByCredentials(username, password);
        if(user != null) {
            AuthorizationData authorizationData = new AuthorizationData();
            authorizationData.setUserId(user.getId());
            Date time = GregorianCalendar.getInstance().getTime();
            authorizationData.setLastAccessTime(time);
            authorizationData.setLoginTime(time);

            String uuid = UUID.randomUUID().toString();
            authDataRedisTemplate.opsForValue().set(uuid, authorizationData);

            return ResponseUtil.successResponse("token", uuid);
        } else {
            return ResponseUtil.errorResponse(ApiError.USER_NOT_FOUND, "user not found");
        }
    }

    private Object logout (HttpServletRequest request, HttpServletResponse response) {
        String token = request.getParameter(AUTH_TOKEN_PARAM);
        if(token != null) {
            authDataRedisTemplate.delete(token);
        }
        return ResponseUtil.successResponse();
    }

    private boolean authorization (HttpServletRequest request, HttpServletResponse response) {
        String token = request.getParameter(AUTH_TOKEN_PARAM);
        if (token != null && !(token = token.trim()).isEmpty()) {
            AuthorizationData authData = (AuthorizationData) authDataRedisTemplate.opsForValue().get(token);
            if (authData != null) {
                authData.setLastAccessTime(GregorianCalendar.getInstance().getTime());
                User user = loginService.findById(authData.getUserId());
                request.setAttribute(Constants.AUTHORIZED_USER_ATTR_NAME, user);
                authDataRedisTemplate.opsForValue().set(token, authData);
                return true;
            }
        }
        return false;
    }

    private void processRequest (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Object resp = dispatchRequest(request, response);
        if (resp != null) {
            Writer writer = null;
            try {
                writer = response.getWriter();
                writer.write(gson.toJson(resp));
            } catch (IOException e) {
                // todo: log it!
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                    }
                }
            }
        }
    }

    @Override
    protected void doGet (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    @Override
    protected void doPost (HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }
}
